﻿using System;
using System.Collections.Generic;
using System.Text;
using ITAP.Core.Modelling.Primitives;
using ITAP.ShprossesBuilder.Calculations;
using ITAP.Core.Modelling;
using ITAP.Core.Calculations;

namespace ITAP.ShprossesBuilder.Modelling.Components.Constraints
{
    [Serializable]
    public class ConnectedComponentsAngleIntervalConstraintForEndedComponent : IConstraint
    {
        private object debugObject = null;
    //    private EndType endTypeOfConnectedComponent;
        ConstraintAction action;

        public ConstraintAction Action
        {
            get { return action; }
        }
        private Type typeOfConnectedComponents;
        private List<AngleInterval> angleIntervals;
        private bool enabled = true;
        public ConnectedComponentsAngleIntervalConstraintForEndedComponent(AngleInterval[] angleIntervals, Type typeOfConnectedComponents, ConstraintAction action)
        {
            this.action = action;
            this.typeOfConnectedComponents = typeOfConnectedComponents;
            this.angleIntervals = new List<AngleInterval>();
            foreach (AngleInterval angleInterval in angleIntervals)
                this.angleIntervals.Add((AngleInterval)angleInterval.Clone());
        }

        #region IConstraint Members

        public bool Check(IComponent component)
        {
            if (this.angleIntervals.Count == 0) return true;
            if (!(component is IEndedComponent)) return true;
            if (SimpleEqualProvider.IsEqual(component.Primitives[0].Length, 0, 0.0001))
                return true;

            IEndedComponent endedComponent = (IEndedComponent)component;

            int angleIntervalCounter = 0;
            int componentsWithType = 0;
            foreach (IComponent connectedComponent in component.ConnectedComponents)
            {
                //if (SimpleEqualProvider.IsEqual(connectedComponent.Primitives[0].Length, 0, 0.00001))
                //    continue;

                // Если тип компонента, для которого определено правило, не совпадает с типом текущего компонента -
                // пропустить его и перейти к следующему.
                if (connectedComponent.GetType() != this.typeOfConnectedComponents) continue;

                IEndedComponent endedConnectedComponent = (IEndedComponent)connectedComponent;                


                Angle angle = null;

                // Компонент - component, текущий компонент - connectedComponent.
                //
                // Начало компонента соединено с текущим компонентом
                if (endedComponent.StartConnectedComponent == endedConnectedComponent)
                {
                    // Начало текущего компонента соединено с началом компонента.
                    if (endedConnectedComponent.StartConnectedComponent == endedComponent)
                    {
                        // Берём в качестве угловой точки конец текущего компонента.
                        ModelVector connectedComponentVector = endedConnectedComponent.Primitives[0].GetTangent(0).Rotate(new Angle(Math.PI));

                        ModelVector componentVector = 
                            endedComponent.Primitives[0].GetTangent(0);
                        angle = ModelCalculations.GetAngleBetweenVectors(componentVector, connectedComponentVector, false);

                    }
                    // Конец текущего компонента соединён с началом компонента.
                    else if (endedConnectedComponent.EndConnectedComponent == endedComponent)
                    {
                        // Берём в качестве угловой точки начало текущего компонента.
                        ModelVector connectedComponentVector = endedConnectedComponent.Primitives[0].GetTangent(1);
                        ModelVector componentVector = endedComponent.Primitives[0].GetTangent(0);

                        angle = ModelCalculations.GetAngleBetweenVectors(componentVector, connectedComponentVector, false);
                    }
                    else
                        throw new Exception();

                }
                // Конец компонента соединён с текущим компонентом
                else if (endedComponent.EndConnectedComponent == endedConnectedComponent)
                {
                    // Начало текущего компонента соединено с концом компонента.
                    if (endedConnectedComponent.StartConnectedComponent == endedComponent)
                    {
                        // Берём в качестве угловой точки конец текущего компонента.
                        ModelVector connectedComponentVector = endedConnectedComponent.Primitives[0].GetTangent(0);

                        ModelVector componentVector = endedComponent.Primitives[0].GetTangent(1);
                        angle = ModelCalculations.GetAngleBetweenVectors(componentVector, connectedComponentVector, false);

                    }
                    // Конец текущего компонента соединён с концом компонента.
                    else if (endedConnectedComponent.EndConnectedComponent == endedComponent)
                    {
                        // Берём в качестве угловой точки начало текущего компонента.
                        ModelVector connectedComponentVector =
                            endedConnectedComponent.Primitives[0].GetTangent(1).Rotate(new Angle(Math.PI));

                        ModelVector componentVector = endedComponent.Primitives[0].GetTangent(1);
                        angle = ModelCalculations.GetAngleBetweenVectors(componentVector, connectedComponentVector, false);
                    }
                    else
                        throw new Exception();
                }
                // Если компонент присоединён к данному компоненту, но не к его концам, то это может означать, что
                // он присоединён "сбоку" (например, StickConnector к Shpross).
                else
                {
                    continue;
                }

                componentsWithType++;

                if (IsAngleIntervalsContainsAngle(angle))
                {
                    angleIntervalCounter++;
                }
            }
          
            if (angleIntervalCounter == this.angleIntervals.Count)
                return true;
            else
            {
                if (componentsWithType == 0)
                    return true;
                else
                    return false;
            }
        }

        public void Fix(IComponent component)
        {

            if (!enabled) return;
            if (this.angleIntervals.Count == 0) return;

            if (!(component is IEndedComponent)) return;
            IEndedComponent endedComponent = (IEndedComponent)component;
            EndType connectedPrimitiveEndForRotate = EndType.NotDefined;

            int index = 0;
            List<IComponent> componentsToDisconnect = new List<IComponent>();
            foreach (IComponent connectedComponent in component.ConnectedComponents)
            {
                if (connectedComponent.GetType() != typeOfConnectedComponents) continue;

                if (action == ConstraintAction.Disconnect)
                {
                    componentsToDisconnect.Add(connectedComponent);
                }
                else
                {
                    IEndedComponent endedConnectedComponent = (IEndedComponent)connectedComponent;
                    Angle angle = null;

                    // Компонент - component, текущий компонент - connectedComponent.
                    //
                    // Начало компонента соединено с текущим компонентом
                    if (endedComponent.StartConnectedComponent == endedConnectedComponent)
                    {
                        // Начало текущего компонента соединено с началом компонента.
                        if (endedConnectedComponent.StartConnectedComponent == endedComponent)
                        {
                            // Берём в качестве угловой точки конец текущего компонента.
                            ModelVector connectedComponentVector = endedConnectedComponent.Primitives[0].GetTangent(1);
                            
                            ModelVector componentVector =
                                endedComponent.Primitives[0].GetTangent(0).Rotate(new Angle(Math.PI));
                            angle = ModelCalculations.GetAngleBetweenVectors(componentVector, connectedComponentVector, false);

                            connectedPrimitiveEndForRotate = EndType.End;

                        }
                        // Конец текущего компонента соединён с началом компонента.
                        else if (endedConnectedComponent.EndConnectedComponent == endedComponent)
                        {
                            // Берём в качестве угловой точки начало текущего компонента.
                            ModelVector connectedComponentVector = endedConnectedComponent.Primitives[0].GetTangent(0);
                            ModelVector componentVector = endedComponent.Primitives[0].GetTangent(0);

                            angle = ModelCalculations.GetAngleBetweenVectors(componentVector, connectedComponentVector, false);
                            connectedPrimitiveEndForRotate = EndType.Start;
                        }
                        else
                            throw new Exception();

                    }
                    // Конец компонента соединён с текущим компонентом
                    else if (endedComponent.EndConnectedComponent == endedConnectedComponent)
                    {
                        // Начало текущего компонента соединено с концом компонента.
                        if (endedConnectedComponent.StartConnectedComponent == endedComponent)
                        {
                            // Берём в качестве угловой точки конец текущего компонента.
                            ModelVector connectedComponentVector = endedConnectedComponent.Primitives[0].GetTangent(1);

                            ModelVector componentVector = endedComponent.Primitives[0].GetTangent(1);
                            angle = ModelCalculations.GetAngleBetweenVectors(componentVector, connectedComponentVector, false);

                            connectedPrimitiveEndForRotate = EndType.End;

                        }
                        // Конец текущего компонента соединён с концом компонента.
                        else if (endedConnectedComponent.EndConnectedComponent == endedComponent)
                        {
                            // Берём в качестве угловой точки начало текущего компонента.
                            ModelVector connectedComponentVector =
                                endedConnectedComponent.Primitives[0].GetTangent(0).Rotate(new Angle(Math.PI));

                            ModelVector componentVector = endedComponent.Primitives[0].GetTangent(1);
                            angle = ModelCalculations.GetAngleBetweenVectors(componentVector, connectedComponentVector, false);

                            connectedPrimitiveEndForRotate = EndType.Start;
                        }
                        else
                            throw new Exception();
                    }
                    else
                    {
                        continue;
                    }

                    Angle newAngle = (Angle)angle.Clone();
                    if (!IsAngleIntervalsContainsAngle(angle))
                    {

                        newAngle = this.FindNearestAngle(angle);

                        double angleToRotate = AngleInterval.GetAngleFromEndToStart(angle, newAngle);
                        //Logger.Write("       Rotate " + connectedComponent.GetType().Name + " " + connectedComponent.Name + " " + angleToRotate.ToString());
                        foreach (IPrimitive primitive in connectedComponent.Primitives)
                        {
                            if(connectedPrimitiveEndForRotate == EndType.Start)
                                primitive.Rotate(primitive.GetPoint(0), angleToRotate);
                            else
                                primitive.Rotate(primitive.GetPoint(1), angleToRotate);
                        }
                    }
                    index++;
                }
            }
            if(action == ConstraintAction.Disconnect)
                foreach(IComponent componentToDisconnect in componentsToDisconnect)
                    ConnectionProvider.Disconnect(component, componentToDisconnect);
        }

        private Angle FindNearestAngle(Angle angle)
        {
            Angle nearestAngle = new Angle(2 * Math.PI - 0.01);
            Angle currentNearestAngle = new Angle(0);
            Angle currentDiff = null;
            Angle minDiff = new Angle(2 * Math.PI - 0.01);
            foreach (AngleInterval _angleInterval in this.angleIntervals)
            {
             //   AngleInterval _angleInterval = this.angleIntervals[index];

                Angle startAngleDiff = ModelCalculations.Min<Angle>(_angleInterval.StartAngle - angle, angle - _angleInterval.StartAngle);
                Angle endAngleDiff = ModelCalculations.Min<Angle>(_angleInterval.EndAngle - angle, angle - _angleInterval.EndAngle);
                if (startAngleDiff.CompareTo(endAngleDiff) == -1)
                {
                    currentDiff = (Angle)startAngleDiff.Clone();
                    currentNearestAngle = (Angle)_angleInterval.StartAngle.Clone();
                }
                else
                {
                    currentDiff = (Angle)endAngleDiff.Clone();
                    currentNearestAngle = (Angle)_angleInterval.EndAngle.Clone();
                }

                if (currentDiff.CompareTo(minDiff) == -1)
                {
                    minDiff = (Angle)currentDiff.Clone();
                    nearestAngle = (Angle)currentNearestAngle.Clone();
                }
            }
            //}
            return nearestAngle;
        }

        private bool IsAngleIntervalsContainsAngle(Angle angle)
        {
            foreach(AngleInterval interval in this.angleIntervals)
                if(interval.IsAngleInInterval(angle))
                    return true;

            return false;
            //bool isInInterval = this.angleIntervals[index].IsAngleInInterval(angle);
            //return isInInterval;
        }


        public bool Enabled
        {
            get
            {
                return enabled;
            }
            set
            {
                enabled = value;
            }
        }

        #endregion

   
        #region ICloneable Members

        public object Clone()
        {
            List<AngleInterval> intervals = new List<AngleInterval>();
            foreach(AngleInterval angleInterval in this.angleIntervals)
                intervals.Add(angleInterval);
            ConnectedComponentsAngleIntervalConstraintForEndedComponent clone = new ConnectedComponentsAngleIntervalConstraintForEndedComponent(intervals.ToArray(), typeOfConnectedComponents, action);
            return clone;
        }

        #endregion
    }
}
